#include <stdio.h>
#include <stdarg.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string>
#include <iostream>
#include <memory>
#include <unordered_set>
#include "xmlop.h"
#include "tinyxml2.h"
#include "pugixml.hpp"

using namespace tinyxml2;
using namespace pugi;
using namespace std;

struct xmlop_data 
{
    XMLDocument *doc;
};

struct pugi_xmlop_data
{
    xml_document *doc;
};

static inline int connect_configd()
{
    struct sockaddr_in cli;
    int configd_fd = -1;
    cli.sin_family = AF_INET;
    cli.sin_port = htons(8088);
    cli.sin_addr.s_addr = inet_addr("127.0.0.1");
    configd_fd = socket(AF_INET, SOCK_STREAM, 0);   
    if (configd_fd < 0) {
        perror("Netwrok test...\n");
        return -1;
    }   

    if (connect(configd_fd, (struct sockaddr *)&cli, sizeof(cli)) != 0) 
    {
        if (errno != EINPROGRESS) { // EINPROGRESS 
            perror("Network test...\n");
            close(configd_fd);
            return -1;
        }   
    }
    return configd_fd;
}

static inline void close_configd(int fd)
{
    if (fd >= 0)
        close(fd);
}

static inline int SendWaitReply(const char *filepath, int fd, int time_ms, std::string &send_reply)
{
    char len_buf[11] = ""; 
    char buffer[4096]="";

    if (fd < 0) 
    {   
        return -1;
    }

    int ret = send(fd, send_reply.c_str(), send_reply.length(), 0); 
    if (ret < 0)
    {
        printf("try send data to configd failed. xml=%s, err=%d, str=%s.\n", 
                    filepath, errno, send_reply.c_str());
        return -1;
    }
    int len = read(fd, len_buf, 10);
    if (len != 10)
    {
        printf("read len data from configd wrong. xml=%s, err=%d, len=%d.\n",
                    filepath, errno, len);
        return 0;
    }
    len_buf[len] = '\0';

    int read_len = atoi(len_buf);
    if(read_len < 0)
    {
        printf("read len data from configd wrong 1. xml=%s, err=%d, len=%d, data=%.*s.\n",
                    filepath, errno, len, len, len_buf);
        return -1; 
    }

    send_reply = ""; 
    while (read_len > 0)
    {
        int len = read(fd, buffer,sizeof(buffer)-1);
        if (len > 0)
        {
            buffer[len] = '\0';
            read_len = read_len - len;
            send_reply = send_reply + buffer;
        }
    }
    return 0;
}


static inline int get_data_from_configd(const char *filepath, std::string &strWrite)
{
    int fd;

    fd = connect_configd();
    if (fd < 0)
    {
        printf("try connect to configd failed. xml=%s, err=%d.\n", filepath, errno);
        return -1;
    }

    int ret = SendWaitReply(filepath, fd, 1000, strWrite);
    if (ret < 0) {
        close_configd(fd);
        return -1;
    }  			
    close_configd(fd);
    printf("get xml data from configd success. xml=%s.\n", filepath);
    return 0;
}

static inline int parse_server_xml(struct xmlop_data *xd, const char *filepath)
{
    int ret;
    std::string strWrite = "{\"filename\":\""+std::string(filepath)+"\",\"func\":\"load\",\"vsysid\":\"-1\",\"xmlstr\":\"\"}";

    ret = get_data_from_configd(filepath, strWrite);
    if (ret < 0)
        return -1;
    
    printf("xml file content. xml=%s, content=%s.\n", filepath, strWrite.c_str());
    return xd->doc->Parse(strWrite.c_str(), strWrite.length());
}

extern "C" xmlop_t xmlop_open(const char *filepath)
{
    struct xmlop_data *xd;
    int ret;

    xd = (struct xmlop_data *) calloc(1, sizeof(struct xmlop_data));
    if (xd == NULL)
        return NULL;

    xd->doc = new XMLDocument();

#if 0
    // server的配置文件需要从configd后台进程获取。  
    if (strncmp(filepath, "/opt/nsfocus/config/sys", sizeof("/opt/nsfocus/config/sys")-1) == 0)
    {
        ret = parse_server_xml(xd, filepath);
        printf("try parse server config file. xml=%s, ret=%d.\n", filepath, ret);
    }
    else
#endif
    {
        ret = xd->doc->LoadFile(filepath);
        printf("try load config file. xml=%s, ret=%d.\n", filepath, ret);
    }
    
    if (ret != 0)
    {
        delete xd->doc;
        free(xd);
        return NULL;
    }

    return xd;
}

extern "C" void xmlop_close(xmlop_t x)
{
    struct xmlop_data *xd = x;

    if (!xd)
        return ;

    delete xd->doc;

    free(xd);
}

void *xmlop_get_elm(xmlop_t x, ...)
{
    va_list va;
    struct xmlop_data *xd = x;

    va_start(va, x);

    XMLHandle cur(xd->doc);
    while (1)
    {
        char *f = va_arg (va, char *);
        if (f == NULL)
            break;

        cur = cur.FirstChildElement(f);
    }

    va_end(va);

    XMLElement *text = cur.ToElement(); 
    if (text == NULL)
        return NULL;
    
    return (void *)text;
}

extern "C" const char *xmlop_get(xmlop_t x, ...)
{
    va_list va;
    struct xmlop_data *xd = x;

    va_start(va, x);

    XMLHandle cur(xd->doc);
    while (1)
    {
        char *f = va_arg (va, char *);
        if (f == NULL)
            break;

        cur = cur.FirstChildElement(f);
    }

    va_end(va);

    XMLElement *text = cur.ToElement(); 
    if (text == NULL)
        return NULL;
    
    return text->GetText();
}

extern "C" void *xmlop_get_next(xmlop_t x, void *prev, ...)
{
    va_list va;
    struct xmlop_data *xd = x;

    if (prev != NULL)
    {
        XMLHandle cur((XMLElement *)prev);

        cur = cur.NextSibling();
        XMLElement *text = cur.ToElement();
        if (text == NULL)
            return NULL;
        return (void *)text;
    }

    va_start(va, prev);

    XMLHandle cur(xd->doc);
    while (1)
    {
        char *f = va_arg (va, char *);
        if (f == NULL)
            break;

        cur = cur.FirstChildElement(f);
    }
    va_end(va);

    XMLElement *text = cur.ToElement(); 
    if (text == NULL)
        return NULL;
    
    return (void *)text;
}

extern "C" void *xmlop_get_sub_next(xmlop_t x, void *sub, void *prev)
{
    if (sub == NULL)
        return NULL;

    if (prev != NULL)
    {
        XMLElement *elm = (XMLElement *)prev;
        XMLElement *text = elm->NextSiblingElement();
        if (text == NULL)
            return NULL;
        cout << "prev address " << prev << " prev name " << elm->Name() << endl;
        cout << " text->name() = " << text->Name() << " elm address " << elm << endl;
        return (void *)text;
    }
    
    XMLElement *elm = (XMLElement *)sub;
    XMLElement *text = elm->FirstChildElement();
    if (text == NULL)
        return NULL;
    cout << "text->name() = " << text->Name() << " elm address " << elm << endl;
    return (void *)text;
}

extern "C" void *xmlop_get_sub_elm(xmlop_t x, void * elm, const char * sub_elm_name)
{
    if (elm == NULL)
        return NULL;

    XMLHandle cur((XMLElement *)elm);
    XMLElement *text1 = cur.ToElement();
    if (text1 == NULL)
    {
        return NULL;
    }
    cout << "text1->name() = " << text1->Name() << " elm address " << elm << endl;

    cur = cur.FirstChildElement(sub_elm_name);
    XMLElement *text = cur.ToElement();
    if (text == NULL)
            return NULL;
    cout << "text->name() = " << text->Name() << " sub elm address " << text << endl;
    return (void *)text;
}

extern "C" const char *xmlop_get_attr(xmlop_t x, void *element, const char *attr)
{
    XMLElement *elm = (XMLElement *) element;

    if (elm == NULL)
        return NULL;

    return elm->Attribute(attr);
}

extern "C" const char *xmlop_get_text(xmlop_t x, void *element)
{
    XMLElement *elm = (XMLElement *) element;

    if (elm == NULL)
        return NULL;

    return elm->GetText();
}

extern "C" pugi_xmlop_t pugi_xmlop_open(const char *filepath)
{
    struct pugi_xmlop_data *xd;

    xd = (struct pugi_xmlop_data *) calloc(1, sizeof(struct pugi_xmlop_data));
    if (xd == NULL)
        return NULL;

    xd->doc = new xml_document();

    if (xd->doc->empty())
    {
        printf("doc is empty,init fail. xml=%s.\n", filepath);
        free(xd);
        return NULL;
    }

    xml_parse_result result = xd->doc->load_file(filepath);
    printf("try load config file. xml=%s.\n", filepath);
    if (!result) 
    {
        printf("try load config file failed. xml=%s.\n", filepath);
        delete xd->doc;
        free(xd);
        return NULL;
    }

    return xd;
}

extern "C" void pugi_xmlop_close(pugi_xmlop_t x)
{
    struct pugi_xmlop_data *xd = x;

    if (!xd)
        return ;

    delete xd->doc;
    free(xd);   
}

void *pugi_xmlop_get_elm(pugi_xmlop_t x, ...)
{
    va_list va;
    struct pugi_xmlop_data *xd = x;

    va_start(va, x);

    xml_node node = xd->doc->root();
    cout << "pugi_xmlop_get_elm root node.name() = " << node.name() << endl;
    while (1)
    {
        char *f = va_arg (va, char *);
        if (f == NULL)
            break;
        node = node.child(f);
        cout << "pugi_xmlop_get_elm node.name() = " << node.name() << endl;
    }

    va_end(va);

    if (node.empty())
    {
        cout << "node.empty()" << endl;
        return NULL;
    }

    return static_cast<void*>(node.internal_object());
    // xml_node * node_ptr = new xml_node(node);
    // add_ptr(xd->manager, node_ptr);
    // return static_cast<void*>(node_ptr);
}

extern "C" const char *pugi_xmlop_get(pugi_xmlop_t x, ...)
{
    va_list va;
    struct pugi_xmlop_data *xd = x;

    va_start(va, x);

    xml_node node = xd->doc->root();
    cout << "pugi_xmlop_get root node.name() = " << node.name() << endl;
    while (1)
    {
        char *f = va_arg (va, char *);
        if (f == NULL)
            break;
        node = node.child(f);
        cout << "node.name() = " << node.name() << endl;
    }

    va_end(va);
    if (node.empty() || node.text().empty())
    {
        cout << "node.empty() or node.tex().empty()" << endl;
        return NULL;
    }
    cout << "node.text() = " << node.text().get() << endl;
    return (char *)node.text().get();
}

extern "C" void *pugi_xmlop_get_next(pugi_xmlop_t x, void *prev, ...)
{
    va_list va;
    struct pugi_xmlop_data *xd = x;

    if (prev != NULL)
    {
        xml_node node = xml_node((xml_node_struct *)prev).next_sibling();

        if (node.empty())
        {
            cout << "node.empty()" << endl;
            return NULL;
        }
        cout << "prev = " << prev << ";" << "node.name() = " << node.name() << endl;
        
        return static_cast<void*>(node.internal_object());
    }

    va_start(va, prev);

    xml_node node = xd->doc->root();
    cout << "pugi_xmlop_get_next root node.name() = " << node.name() << endl;
    while (1)
    {
        char *f = va_arg (va, char *);
        if (f == NULL)
            break;
        node = node.child(f);
        cout << "node.name() = " << node.name() << endl;
    }

    va_end(va);
    if (node.empty())
    {
        cout << "node.empty()" << endl;
        return NULL;
    }
    return static_cast<void*>(node.internal_object());
}

extern "C" const char *pugi_xmlop_get_attr(pugi_xmlop_t x, void *element, const char *attr)
{
    xml_node elm = xml_node((xml_node_struct *)element);
    if (elm.empty())
    {
        cout << "elm.empty()" << endl;
        return NULL;
    }

    if (elm.attribute(attr).empty())
        return NULL;
    return elm.attribute(attr).value();
}

extern "C" const char *pugi_xmlop_get_text(pugi_xmlop_t x, void *element)
{
    xml_node elm = xml_node((xml_node_struct *)element);
    if (elm.empty())
    {
        cout << "elm.empty()" << endl;
        return NULL;
    }

    if (elm.text().empty())
        return NULL;

    return elm.text().get();
}

extern "C" void *pugi_xmlop_get_sub_elm(pugi_xmlop_t x, void * elm, const char * sub_elm_name)
{
    if (elm == NULL)
        return NULL;
    cout << "sub_elm_name = " << sub_elm_name << endl;
    xml_node node = xml_node((xml_node_struct *)elm).child(sub_elm_name);
    if (node.empty())
    {
        cout << "elm.empty()" << endl;
        return NULL;
    }

    return static_cast<void*>(node.internal_object());
}

extern "C" void *pugi_xmlop_get_sub_next(pugi_xmlop_t x, void *sub, void *prev)
{
    if (sub == NULL)
        return NULL;

    if (prev != NULL)
    {
        xml_node node = xml_node((xml_node_struct *)prev).next_sibling();
        if (node.empty())
        {
            cout << "pugi_xmlop_get_sub_next node.empty()" << endl;
            return NULL;
        }
        cout << "prev = " << prev << ";" << "node.name() = " << node.name() << endl;

        return static_cast<void*>(node.internal_object());
    }
    
    xml_node node = xml_node((xml_node_struct *)sub).first_child();
    if (node.empty())
    {
        cout << "pugi_xmlop_get_sub_next node.empty()" << endl;
        return NULL;
    }
    cout << "node.name() = " << node.name() << endl;
    return static_cast<void*>(node.internal_object());
}

extern "C" char *pugi_xpath_get_text(pugi_xmlop_t x, const char *path)
{
    if (!x ||!path)
        return NULL;

    struct pugi_xmlop_data *xd = x;

    // xpath_query query_remote_tools("/ruleFile/globalDef/configs/section/config[@name='router_config']");
    xpath_node_set tools = xd->doc->select_nodes(path);
    if (tools.size() == 0)
    {
        cout << "null" << endl;
        return NULL;
    }

    return (char *)tools[0].node().text().get();
}


extern "C" uint32_t xmlop_xpath_get_text_batch(pugi_xmlop_t x, const char *path, char **result, uint32_t node_num)
{
    if (!x || !path) 
        return 0;

    struct pugi_xmlop_data *xd = x;
    xpath_node_set nodes = xd->doc->select_nodes(path);
    uint32_t node_count = nodes.size();
    
    if (node_count == 0)
    {
        printf("node_count == 0\n");
        return 0;
    }

    if (node_num < node_count)
    {
        printf("node_num %u < node_count %u\n", node_num, node_count);
        return 0;
    }

    size_t i = 0;
    for (xpath_node_set::const_iterator it = nodes.begin(); it != nodes.end(); ++it,i++)
    {
        xpath_node node = *it;
        auto text = node.node().text();
        printf("node %s\n",node.node().name());
        result[i] = (char *)text.get();
    }

    // for (size_t i = 0; i < node_count; i++)
    // {
    //     xml_node node = nodes[i].node();
    //     if (node.empty())
    //     {
    //         printf("node.empty()\n");
    //         return 0;
    //     }
    //     auto text = node.text();
    //     if ((!text.empty()))
    //     {
    //         printf("node %s\n",node.name());
    //         result[i] = (char *)text.get();
    //     }
    // }
    
    return node_count;
}


extern "C" uint32_t xmlop_xpath_get_attr_batch(pugi_xmlop_t x, const char *path, const char *attr, char **result, uint32_t node_num)
{
    if (!x || !path || !attr || !node_num) 
        return 0;

    printf("xmlop_xpath_get_attr_batch path = %s, attr = %s\n",path,attr);
    struct pugi_xmlop_data *xd = x;
    xpath_node_set nodes = xd->doc->select_nodes(path);
    uint32_t node_count = nodes.size();

    printf("node_count = %u\n",node_count);
    
    if (node_count == 0)
    {
        printf("node_count == 0\n");
        return 0;
    }

    if (node_num < node_count)
    {
        printf("node_num %u < node_count %u\n", node_num, node_count);
        return 0;
    }

    size_t i = 0;
    for (xpath_node_set::const_iterator it = nodes.begin(); it != nodes.end(); ++it,i++)
    {
        xpath_node node = *it;
        printf("node %s\n",node.node().name());
        printf("node %s attr %s\n",node.node().name(),node.node().attribute(attr).value());
        result[i] = (char *)node.node().attribute(attr).value();
    }
    
    return node_count;
}


